package com.gitee.wsl.flow.create.state


import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.Snapshot
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlin.test.Test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlin.coroutines.CoroutineContext
import kotlin.test.assertEquals
import kotlin.test.assertFails

@ExperimentalCoroutinesApi
class MoleculeStateFlowTest {
    @Test fun items() = runTest {
        val job = Job()
        val clock = BroadcastFrameClock()
        val scope = CoroutineScope(coroutineContext + job + clock)

        val flow = scope.launchMolecule {
            var count by remember { mutableIntStateOf(0) }
            LaunchedEffect(Unit) {
                while (true) {
                    delay(100)
                    count++
                }
            }

            count
        }

        assertEquals(flow.value,0)

        clock.sendFrame(0)
        assertEquals(flow.value,0)

        advanceTimeBy(99)
        runCurrent()
        clock.sendFrame(0)
        assertEquals(flow.value,0)

        advanceTimeBy(1)
        runCurrent()
        clock.sendFrame(0)
        assertEquals(flow.value,1)

        advanceTimeBy(100)
        runCurrent()
        clock.sendFrame(0)
        assertEquals(flow.value,2)

        job.cancel()
    }

    @Test fun errorImmediately() {
        val clock = BroadcastFrameClock()
        val scope = CoroutineScope(clock)

        // Use a custom subtype to prevent coroutines from breaking referential equality.
        val runtimeException = object : RuntimeException() {}
        assertFails {
            scope.launchMolecule {
                throw runtimeException
            }
        }

        scope.cancel()
    }

    @Test fun errorDelayed() = runTest {
        val job = Job()
        val clock = BroadcastFrameClock()
        val exceptionHandler = RecordingExceptionHandler()
        val scope = CoroutineScope(coroutineContext + job + clock + exceptionHandler)

        // Use a custom subtype to prevent coroutines from breaking referential equality.
        val runtimeException = object : RuntimeException() {}
        var count by mutableIntStateOf(0)
        val flow = scope.launchMolecule {
            println("Sup $count")
            if (count == 1) {
                throw runtimeException
            }
            count
        }

        assertEquals(flow.value,0)

        count++
        Snapshot.sendApplyNotifications() // Ensure external state mutation is observed.
        runCurrent()
        clock.sendFrame(0)
        runCurrent()

        assertEquals(exceptionHandler.exceptions.single(),runtimeException)

        job.cancel()
    }

    @Test fun errorInEffect() = runTest {
        val job = Job()
        val clock = BroadcastFrameClock()
        val exceptionHandler = RecordingExceptionHandler()
        val scope = CoroutineScope(coroutineContext + job + clock + exceptionHandler)

        // Use a custom subtype to prevent coroutines from breaking referential equality.
        val runtimeException = object : RuntimeException() {}
        val flow = scope.launchMolecule{
            LaunchedEffect(Unit) {
                delay(50)
                throw runtimeException
            }
            0
        }

        assertEquals(flow.value,0)

        advanceTimeBy(50)
        runCurrent()
        clock.sendFrame(0)
        assertEquals(exceptionHandler.exceptions.single(),runtimeException)

        job.cancel()
    }

   /* @Test
    fun itemsImmediate() = runTest {
        val job = Job(coroutineContext.job)
        val scope = this + job

        val flow = scope.launchMolecule(Immediate) {
            var count by remember { mutableIntStateOf(0) }
            LaunchedEffect(Unit) {
                while (true) {
                    delay(100)
                    count++
                }
            }

            count
        }

        assertThat(flow.value).isEqualTo(0)

        advanceTimeBy(99)
        runCurrent()
        assertThat(flow.value).isEqualTo(0)

        advanceTimeBy(1)
        runCurrent()
        assertThat(flow.value).isEqualTo(1)

        advanceTimeBy(100)
        runCurrent()
        assertThat(flow.value).isEqualTo(2)

        job.cancel()
    }

    @Test fun errorDelayedImmediate() = runTest {
        // Use a custom subtype to prevent coroutines from breaking referential equality.
        val runtimeException = object : RuntimeException() {}
        val exceptionHandler = RecordingExceptionHandler()

        var count by mutableIntStateOf(0)
        supervisorScope {
            var flow: StateFlow<Int>? = null

            val job = launch(start = CoroutineStart.UNDISPATCHED, context = exceptionHandler) {
                flow = launchMolecule(Immediate) {
                    if (count == 1) {
                        throw runtimeException
                    }
                    count
                }
            }

            assertThat(flow!!.value).isEqualTo(0)

            count++

            job.join()

            assertThat(exceptionHandler.exceptions.single()).isSameInstanceAs(runtimeException)
        }
    }*/
}

class RecordingExceptionHandler : CoroutineExceptionHandler {
    val exceptions = mutableListOf<Throwable>()

    override fun handleException(context: CoroutineContext, exception: Throwable) {
        exceptions += exception
    }

    override val key get() = CoroutineExceptionHandler
}